package com.sukaiyi.giteejavaapi.apifetcher;

import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class HttpFormatApiFetcher {

    public static final String API_DOC_URL = "https://gitee.com/api/v5/swagger";
    public static final String DIST = System.getProperty("user.dir") + "\\src\\main\\resources\\apis";


    public static final List<String> HEADERS = Arrays.asList(
            "Connection: close",
            "Accept: application/json, text/plain, */*",
            "User-Agent: {{user_agent}}"
    );

    private static WebDriver driver;

    @BeforeAll
    static void setupWebDriver() {
        System.setProperty("webdriver.chrome.driver", ".drivers\\chromedriver.exe");
        driver = new ChromeDriver();
        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    }

    @AfterAll
    static void prepareForQuit() {
        driver.quit();
    }

    @TestFactory
    public Stream<DynamicNode> fetch() {
        driver.get(API_DOC_URL);

        List<WebElement> parts = driver.findElement(By.className("olist"))
                .findElements(By.xpath("./section/ul/li"));

        return parts.stream()
                .map(part -> {
                    String partName = part.findElement(By.xpath("./p")).getText();
                    return DynamicContainer.dynamicContainer(partName, handlePart(part));
                });
    }

    private Stream<DynamicNode> handlePart(WebElement part) {
        part.click();
        String partName = part.findElement(By.xpath("./p")).getText();
        String fileName = Optional.of(part)
                .map(p -> p.findElement(By.xpath("./p/span")))
                .map(WebElement::getText)
                .map(String::toLowerCase)
                .map(e -> e.replace(' ', '-'))
                .map(p -> DIST + File.separator + p + ".http")
                .orElse(null);
        if (fileName == null) {
            return Stream.empty();
        }
        List<WebElement> items = part.findElements(By.xpath("./ul/a"));

        File file = new File(fileName);
        Path path = file.toPath();
        try {
            if (!file.exists() && !file.createNewFile()) {
                return Stream.empty();
            }
            Files.write(path, ("# " + partName + " #\n\n").getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
            return Stream.empty();
        }
        return items.stream()
                .map(item -> {
                    String description = item.getText();
                    return DynamicTest.dynamicTest(description, () -> handleItem(path, item));
                });
    }

    private void handleItem(Path path, WebElement item) throws IOException {
        String description = item.getText();
        String itemUrl = item.getAttribute("href");
        driver.get(itemUrl);
        String name = itemUrl.substring(itemUrl.lastIndexOf('/') + 1);

        WebElement addressEle = driver.findElement(By.xpath("//div[@class=\"address\"]"));
        String method = addressEle.findElement(By.tagName("span")).getText();
        String url = addressEle.findElement(By.tagName("p")).getText();
        url = url.replace("{", "{{")
                .replace("}", "}}")
                .replace("https://gitee.com/api/v5", "{{base_url}}");

        List<WebElement> parameterEles = driver.findElement(By.xpath("//div[@class=\"parameters\"]"))
                .findElement(By.xpath("//tbody"))
                .findElements(By.tagName("tr"));
        List<Param> params = parameterEles.stream()
                .map(p -> p.findElements(By.xpath("./td")))
                .map(eles -> {
                    String paramName = eles.get(0).getText().replace("*", "");
                    String desc = eles.get(2).getText();
                    String type = eles.get(3).getText();
                    String dataType = eles.get(4).getText();
                    return new Param(paramName, desc, type, dataType);
                }).collect(Collectors.toList());

        String http = buildHttp(name, description, method, url, params);
        Files.write(path, (http + "\n\n").getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
        System.out.println(http + "\n\n");
    }

    private String buildHttp(String name, String description, String method, String url, List<Param> params) {
        String paramsStr = params.stream()
                .filter(param -> Objects.equals(param.type, "query"))
                .map(param -> param.name)
                .map(paramName -> String.format("%s={{%s}}", paramName, paramName))
                .collect(Collectors.joining("&"));
        if (!paramsStr.isEmpty()) {
            url = url + "?" + paramsStr;
        }

        StringBuilder sb = new StringBuilder();
        sb.append("### ").append(description).append('\n')
                .append("# Name: ").append(name).append('\n')
                .append("# Parameters:\n");
        for (Param param : params) {
            sb.append("# ").append(param.name).append('(').append(param.dataType).append(')')
                    .append('\t').append(param.description).append('\n');
        }
        sb.append(method).append(' ').append(url).append('\n');
        for (String header : HEADERS) {
            sb.append(header).append('\n');
        }
        if (Arrays.asList("PUT", "POST", "DELETE", "PATCH").contains(method)) {
            String body = params.stream()
                    .filter(param -> Objects.equals(param.type, "formData"))
                    .map(param -> {
                        boolean isString = Objects.equals(param.dataType, "string");
                        return String.format(isString ? "    \"%s\":\"{{%s}}\"" : "    \"%s\":{{%s}}", param.name, param.name);
                    })
                    .collect(Collectors.joining(",\n"));
            if (!body.isEmpty()) {
                sb.append("Content-Type: application/json\n\n").append("{\n");
                sb.append(body).append("\n}");
            }
        }
        return sb.toString();
    }

    public static final class Param {
        private String name;
        private String description;
        private String type;
        private String dataType;

        public Param(String name, String description, String type, String dataType) {
            this.name = name;
            this.description = description;
            this.type = type;
            this.dataType = dataType;
        }
    }
}